// @flow
import { type I18n as I18nType } from '@lingui/core';
import * as React from 'react';
import ListIcon from '../UI/ListIcon';
import Subheader from '../UI/Subheader';
import { List, ListItem } from '../UI/List';
import {
  enumerateObjectTypes,
  type EnumeratedObjectMetadata,
} from '../ObjectsList/EnumerateObjects';
import { Column, Line } from '../UI/Grid';
import { sendNewObjectCreated } from '../Utils/Analytics/EventSender';
import ScrollView from '../UI/ScrollView';
import useDismissableTutorialMessage from '../Hints/useDismissableTutorialMessage';
import { AssetStoreContext } from './AssetStoreContext';
import { translateExtensionCategory } from '../Utils/Extension/ExtensionCategories';
import { type ChosenCategory } from '../UI/Search/FiltersChooser';
import { type AssetShortHeader } from '../Utils/GDevelopServices/Asset';
import TextButton from '../UI/TextButton';
import { t, Trans } from '@lingui/macro';
import ChevronArrowLeft from '../UI/CustomSvgIcons/ChevronArrowLeft';
import AssetsList from './AssetsList';

const gd: libGDevelop = global.gd;

const ObjectListItem = ({
  enumeratedObjectMetadata,
  onClick,
  id,
}: {|
  enumeratedObjectMetadata: EnumeratedObjectMetadata,
  onClick: () => void,
  id?: string,
|}) => {
  if (enumeratedObjectMetadata.name === '') {
    // Base object is an "abstract" object
    return null;
  }

  // For some reason, iconFileName can sometimes be undefined. see https://github.com/4ian/GDevelop/issues/6094.
  const iconFilename = enumeratedObjectMetadata.iconFilename || '';

  return (
    <ListItem
      id={id}
      leftIcon={<ListIcon src={iconFilename} iconSize={40} isGDevelopIcon />}
      key={enumeratedObjectMetadata.name}
      primaryText={enumeratedObjectMetadata.fullName}
      secondaryText={enumeratedObjectMetadata.description}
      secondaryTextLines={2}
      onClick={onClick}
      disabled={!!enumeratedObjectMetadata.isDependentWithParent}
    />
  );
};

type CustomObjectPackResultsProps = {|
  packTag: string,
  onAssetSelect: (assetShortHeader: AssetShortHeader) => Promise<void>,
  onBack: () => void,
  isAssetBeingInstalled: boolean,
|};

export const CustomObjectPackResults = ({
  packTag,
  onAssetSelect,
  onBack,
  isAssetBeingInstalled,
}: CustomObjectPackResultsProps) => {
  const { useSearchItem, error } = React.useContext(AssetStoreContext);
  // Memoizing the parameters of the search as it seems to trigger infinite rendering if not.
  const chosenCategory: ChosenCategory = React.useMemo(
    () => ({
      node: {
        name: packTag,
        allChildrenTags: [],
        children: [],
      },
      parentNodes: [],
    }),
    [packTag]
  );
  const filters = React.useMemo(() => [], []);
  const selectedAssetPackSearchResults = useSearchItem(
    '',
    chosenCategory,
    null,
    filters
  );

  return (
    <>
      <Column noMargin expand>
        <Line>
          <TextButton
            icon={<ChevronArrowLeft />}
            label={<Trans>Back</Trans>}
            onClick={onBack}
            disabled={isAssetBeingInstalled}
          />
        </Line>
        <AssetsList
          assetShortHeaders={selectedAssetPackSearchResults}
          error={error}
          onOpenDetails={assetShortHeader => {
            if (isAssetBeingInstalled) return;
            onAssetSelect(assetShortHeader);
          }}
        />
      </Column>
    </>
  );
};

const getMergedInstalledWithDefaultEnumeratedObjectMetadataByCategory = ({
  i18n,
  project,
  eventsBasedObject,
}: {|
  i18n: I18nType,
  project: gdProject,
  eventsBasedObject: gdEventsBasedObject | null,
|}): { [key: string]: Array<EnumeratedObjectMetadata> } => {
  const installedEnumeratedObjectMetadatas = enumerateObjectTypes(project);

  // - Objects with only a name defined are built in, so will be replaced
  //   by the real object metadata when we loop through the installed objects.
  // - Objects with more information are custom objects and may not be installed,
  //   so we add as much info as possible so that the user can see what they are.
  const defaultEnumeratedObjectMetadatasByCategory = {
    [translateExtensionCategory('General', i18n)]: [
      {
        name: 'Sprite',
      },
      {
        name: 'TiledSpriteObject::TiledSprite',
      },
      {
        name: 'PanelSpriteObject::PanelSprite',
      },
      {
        name: 'Scene3D::Cube3DObject',
      },
      {
        name: 'Scene3D::Model3DObject',
      },
      eventsBasedObject && {
        name: 'Sprite3D::Sprite3D',
        fullName: i18n._(t`3D sprite`),
        description: i18n._(t`An animated sprite in 3D.`),
        iconFilename:
          '',
        isRenderedIn3D: true,
        requiredExtensions: [
          {
            extensionName: 'Sprite3D',
            extensionVersion: '1.0.0',
          },
        ],
      },
    ].filter(Boolean),
    [translateExtensionCategory('Input', i18n)]: [
      {
        name: 'SpriteMultitouchJoystick::SpriteMultitouchJoystick',
        fullName: i18n._(t`Multitouch Joystick`),
        description: i18n._(t`Joystick for touchscreens.`),
        iconFilename:
          '',
        isRenderedIn3D: false,
        assetStorePackTag: 'multitouch joysticks',
        requiredExtensions: [
          {
            extensionName: 'SpriteMultitouchJoystick',
            extensionVersion: '1.0.0',
          },
        ],
      },
    ],
    [translateExtensionCategory('Text', i18n)]: [
      {
        name: 'TextObject::Text',
      },
      {
        name: 'BBText::BBText',
      },
      {
        name: 'BitmapText::BitmapTextObject',
      },
      eventsBasedObject && {
        name: 'Text3D::Text3D',
        fullName: i18n._(t`3D text`),
        description: i18n._(t`Display a text in 3D.`),
        iconFilename:
          '',
        isRenderedIn3D: true,
        requiredExtensions: [
          {
            extensionName: 'Text3D',
            extensionVersion: '1.0.0',
          },
        ],
      },
    ].filter(Boolean),
    [translateExtensionCategory('User interface', i18n)]: [
      {
        name: 'PanelSpriteButton::PanelSpriteButton',
        fullName: i18n._(t`Panel sprite button`),
        description: i18n._(t`Resizable button with text customization.`),
        iconFilename:
          '',
        isRenderedIn3D: false,
        assetStorePackTag: 'menu buttons',
        requiredExtensions: [
          {
            extensionName: 'PanelSpriteButton',
            extensionVersion: '1.0.0',
          },
        ],
      },
      {
        name: 'PanelSpriteSlider::PanelSpriteSlider',
        fullName: i18n._(t`Slider`),
        description: i18n._(
          t`Let users select a numerical value by dragging a slider.`
        ),
        iconFilename:
          '',
        isRenderedIn3D: false,
        assetStorePackTag: 'settings ui',
        requiredExtensions: [
          {
            extensionName: 'PanelSpriteSlider',
            extensionVersion: '1.0.0',
          },
        ],
      },
      {
        name: 'SpriteToggleSwitch::SpriteToggleSwitch',
        fullName: i18n._(t`Toggle switch`),
        description: i18n._(t`A toggle switch that users can click or touch.`),
        iconFilename:
          '',
        isRenderedIn3D: false,
        assetStorePackTag: 'settings ui',
        requiredExtensions: [
          {
            extensionName: 'SpriteToggleSwitch',
            extensionVersion: '1.0.0',
          },
        ],
      },
      {
        name: 'PanelSpriteContinuousBar::PanelSpriteContinuousBar',
        fullName: i18n._(t`Resource bar (continuous)`),
        description: i18n._(
          t`A bar that represents a resource in the game (health, mana, ammo, etc).`
        ),
        iconFilename:
          '',
        isRenderedIn3D: false,
        assetStorePackTag: 'resource bars',
        requiredExtensions: [
          {
            extensionName: 'PanelSpriteContinuousBar',
            extensionVersion: '1.0.0',
          },
        ],
      },
      {
        name: 'TiledUnitsBar::TiledUnitsBar',
        fullName: i18n._(t`Resource bar (separated units)`),
        description: i18n._(
          t`A bar that represents a resource in the game (health, mana, ammo, etc).`
        ),
        iconFilename:
          '',
        isRenderedIn3D: false,
        assetStorePackTag: 'resource bars',
        requiredExtensions: [
          {
            extensionName: 'TiledUnitsBar',
            extensionVersion: '1.0.0',
          },
        ],
      },
      {
        name: 'TextInput::TextInputObject',
      },
      {
        name: 'Video::VideoObject',
      },
    ],
    [translateExtensionCategory('Visual effect', i18n)]: [
      {
        name: 'Lighting::LightObject',
      },
      {
        name: 'ParticleSystem::ParticleEmitter',
        assetStorePackTag: 'particles emitter',
        requiredExtensions: [],
      },
      {
        name: 'ParticleEmitter3D::ParticleEmitter3D',
        fullName: i18n._(t`3D particle emitter`),
        description: i18n._(
          t`Displays a large number of particles to create visual effects.`
        ),
        iconFilename:
          '',
        isRenderedIn3D: true,
        assetStorePackTag: '3d particles',
        requiredExtensions: [
          {
            extensionName: 'ParticleEmitter3D',
            extensionVersion: '1.0.0',
          },
        ],
      },
    ],
    [translateExtensionCategory('Advanced', i18n)]: [
      {
        name: 'TileMap::TileMap',
      },
      {
        name: 'TileMap::CollisionMask',
      },
      {
        name: 'PrimitiveDrawing::Drawer',
      },
    ],
  };
  installedEnumeratedObjectMetadatas.forEach(
    installedEnumeratedObjectMetadata => {
      const category = translateExtensionCategory(
        installedEnumeratedObjectMetadata.categoryFullName,
        i18n
      );
      defaultEnumeratedObjectMetadatasByCategory[category] =
        defaultEnumeratedObjectMetadatasByCategory[category] || [];
      const objectIndex = defaultEnumeratedObjectMetadatasByCategory[
        category
      ].findIndex(
        enumeratedObjectMetadata =>
          enumeratedObjectMetadata.name ===
          installedEnumeratedObjectMetadata.name
      );

      if (objectIndex !== -1) {
        defaultEnumeratedObjectMetadatasByCategory[category][objectIndex] = {
          ...defaultEnumeratedObjectMetadatasByCategory[category][objectIndex],
          ...installedEnumeratedObjectMetadata,
          // Avoid to reinstall the extension every time an object is added.
          requiredExtensions: [],
        };
      } else {
        defaultEnumeratedObjectMetadatasByCategory[category].push(
          installedEnumeratedObjectMetadata
        );
      }
    }
  );

  if (eventsBasedObject) {
    for (const category in defaultEnumeratedObjectMetadatasByCategory) {
      const enumeratedObjectMetadatas =
        defaultEnumeratedObjectMetadatasByCategory[category];
      for (const enumeratedObjectMetadata of enumeratedObjectMetadatas) {
        if (!project.hasEventsBasedObject(enumeratedObjectMetadata.name)) {
          continue;
        }
        const otherEventBasedObject = project.getEventsBasedObject(
          enumeratedObjectMetadata.name
        );
        enumeratedObjectMetadata.isDependentWithParent = gd.EventsBasedObjectDependencyFinder.isDependentFromEventsBasedObject(
          project,
          otherEventBasedObject,
          eventsBasedObject
        );
      }
    }
  }

  return defaultEnumeratedObjectMetadatasByCategory;
};

type Props = {|
  project: gdProject,
  eventsBasedObject: gdEventsBasedObject | null,
  onObjectTypeSelected: EnumeratedObjectMetadata => void,
  i18n: I18nType,
|};

export default function NewObjectFromScratch({
  project,
  eventsBasedObject,
  onObjectTypeSelected,
  i18n,
}: Props) {
  const enumeratedObjectMetadatasByCategory: {
    [string]: Array<EnumeratedObjectMetadata>,
  } = React.useMemo(
    () =>
      getMergedInstalledWithDefaultEnumeratedObjectMetadataByCategory({
        project,
        eventsBasedObject,
        i18n,
      }),
    [project, eventsBasedObject, i18n]
  );

  const { DismissableTutorialMessage } = useDismissableTutorialMessage(
    'intro-object-types'
  );

  return (
    <ScrollView>
      {DismissableTutorialMessage && (
        <Line>
          <Column expand>{DismissableTutorialMessage}</Column>
        </Line>
      )}
      <List>
        {Object.keys(enumeratedObjectMetadatasByCategory).map(category => {
          const categoryEnumeratedObjectMetadatas = enumeratedObjectMetadatasByCategory[
            category
          ].filter(
            enumeratedObjectMetadata =>
              !eventsBasedObject ||
              eventsBasedObject.isRenderedIn3D() ===
                enumeratedObjectMetadata.isRenderedIn3D
          );
          return categoryEnumeratedObjectMetadatas.length === 0 ? null : (
            <React.Fragment key={category}>
              <Subheader>{category}</Subheader>
              {categoryEnumeratedObjectMetadatas.map(
                enumeratedObjectMetadata => (
                  <ObjectListItem
                    key={enumeratedObjectMetadata.name}
                    enumeratedObjectMetadata={enumeratedObjectMetadata}
                    id={`object-category-${
                      enumeratedObjectMetadata.name
                    }`.replace(/:/g, '-')}
                    onClick={() => {
                      sendNewObjectCreated(enumeratedObjectMetadata.name);
                      onObjectTypeSelected(enumeratedObjectMetadata);
                    }}
                  />
                )
              )}
            </React.Fragment>
          );
        })}
      </List>
    </ScrollView>
  );
}
